﻿/*  
	------------------------------------------------------------------------
	 NQ Core Library
	
	 Homepage: http://www.awzhome.de/
	------------------------------------------------------------------------
	
	This Source Code Form is subject to the terms of the Mozilla Public
	License, v. 2.0. If a copy of the MPL was not distributed with this
	file, You can obtain one at http://mozilla.org/MPL/2.0/.
   
	The Original Code is code of NQ Core Library.

	The Initial Developer of the Original Code is Andreas Weizel.
	Portions created by the Initial Developer are
	Copyright (C) 2012 Andreas Weizel. All Rights Reserved.
	
	Contributor(s): (none)
	
	------------------------------------------------------------------------
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.IO;

namespace AWZhome.NQ.Core
{
	/// <summary>
	/// Default component loader implementation, that gets all needed information
	/// about components and services from class attributes.
	/// </summary>
	/// <remarks>
	/// This implementation loads all components in the application directory
	/// that match the "*.Component.dll" and "*.Part.dll" masks.
	/// </remarks>
	public class NQAttributeBasedComponentLoader : INQComponentLoader
	{
		private Dictionary<string, INQComponentInfo> _compInfos = new Dictionary<string, INQComponentInfo>();
		private Dictionary<Type, INQServiceInfo> _servInfos = new Dictionary<Type, INQServiceInfo>();
		private string _componentPath = String.Empty;

		private List<string> _loadedAssemblies = new List<string>();
		private Dictionary<Assembly, NQComponentInfo> _compAssemblies = new Dictionary<Assembly, NQComponentInfo>();

		#region INQComponentLoader Member

		public IEnumerable<INQComponentInfo> LoadComponentData()
		{
			const string cNQCSBasic = "NQCommServBasic.Component.dll";
			const string cNQCS = "NQCommServ.Component.dll";
			List<string> forbidden = new List<string>();

			// Add forbidden assembly names to a searchable list
			if (NQServiceManager.Instance.GetCmdLineParam("cf") != null)
			{
				foreach (string filename in NQServiceManager.Instance.GetCmdLineParam("cf"))
				{
					forbidden.Add(filename.ToLower());
				}
			}

			_componentPath = Environment.CurrentDirectory;

			// Iterate through all component assemblies in the specified directory
			string[] assemblies = NQServiceManager.Instance.GetCmdLineParam("ca");
			if (assemblies == null)
			{
				// Identify all DLLs as component assemblies, whose names end with .Component.dll or .Part.dll
				List<string> componentAssemblies = new List<string>();
				componentAssemblies.AddRange(Directory.GetFiles(_componentPath, "*.Component.dll", SearchOption.TopDirectoryOnly));
				componentAssemblies.AddRange(Directory.GetFiles(_componentPath, "*.Part.dll", SearchOption.TopDirectoryOnly));
				assemblies = new string[componentAssemblies.Count];
				componentAssemblies.CopyTo(assemblies);
			}
			foreach (string dllFile in assemblies)
			{
				if (!forbidden.Contains(Path.GetFileName(dllFile).ToLower()) && (Path.GetFileName(dllFile).ToLower() != cNQCS.ToLower()))
				{
					// Load the single component (we don't know if the path is already in dllFile, so we recombine it)
					this.LoadComponentAssembly(Path.Combine(_componentPath, Path.GetFileName(dllFile)));
				}
			}

			// Load NQCS if existing and not forbidden
			string nqcsDLL = Path.Combine(_componentPath, cNQCSBasic);
			if (File.Exists(nqcsDLL) && !forbidden.Contains(cNQCS.ToLower()))
			{
				this.LoadComponentAssembly(nqcsDLL);
			}
			nqcsDLL = Path.Combine(_componentPath, cNQCS);
			if (File.Exists(nqcsDLL) && !forbidden.Contains(cNQCS.ToLower()))
			{
				this.LoadComponentAssembly(nqcsDLL);
			}

			// Iterate through all loaded assemblies searching for NQ components
			Dictionary<string, List<Assembly>> compParts = new Dictionary<string, List<Assembly>>();
			foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies())
			{
				if (NQComponentInfo.IsComponent(a))
				{
					// Create component info and save it
					NQComponentInfo compInfo = new NQComponentInfo(a);

					if (!compInfo.IsComponentPart)
					{
						// Add component definition to internal list
						if (!_compInfos.ContainsKey(compInfo.Name))
						{
							_compInfos.Add(compInfo.Name, compInfo);
						}
						else
						{
							// Component is already known
							NQServiceManager.Instance.LogMessage(NQLogType.Error, "Multiple definition of component " + compInfo.Name + ".");
							throw new NQComponentDefinitionException(NQErrorReason.ComponentMultiplyDefined, "Multiple definition of component " + compInfo.Name + ".");
						}
					}
					else
					{
						// Add assembly to temporary parts list
						if (!compParts.ContainsKey(compInfo.Name))
						{
							compParts.Add(compInfo.Name, new List<Assembly>());
						}
						compParts[compInfo.Name].Add(a);
					}

					// Save assembly and NQComponentInfo reference in hashtable for later iteration
					_compAssemblies.Add(a, compInfo);
				}
			}

			// Convert part assembly lists to arrays
			foreach (KeyValuePair<string, List<Assembly>> partList in compParts)
			{
				Assembly[] assemblyArray = new Assembly[partList.Value.Count];
				partList.Value.CopyTo(assemblyArray);
				((NQComponentInfo) _compInfos[partList.Key]).PartAssemblies = assemblyArray;
			}

			// Return the ComponentInfo list
			return _compInfos.Values;
		}

		public IEnumerable<INQServiceInfo> LoadServiceData()
		{
			// Now iterate through all well-defined NQ components and search for services inside
			foreach (KeyValuePair<Assembly, NQComponentInfo> nqAssembly in _compAssemblies)
			{
				NQComponentInfo compInfo = nqAssembly.Value;

				Type[] classes = nqAssembly.Key.GetTypes();
				if (classes != null)
				{
					foreach (Type comptype in classes)
					{
						if (NQServiceInfo.IsValidServiceClass(comptype))
						{
							// Firstly create a NQServiceInfo, we'll get all other information from it later
							NQServiceInfo servInfo = new NQServiceInfo(comptype, compInfo.Name);

							if (!_servInfos.ContainsKey(servInfo.ServiceInterface))
							{
								_servInfos.Add(servInfo.ServiceInterface, servInfo);
							}
							else
							{
								throw new NQServiceDefinitionException(NQErrorReason.ServiceMultiplyDefined,
									"Service \"" + servInfo.ServiceInterface + "\" is multiply defined.");
							}
						}
					}
				}
			}

			return _servInfos.Values;
		}

		public string GetComponentPath()
		{
			return _componentPath;
		}

		public bool LoadComponentAssemblies(INQComponentInfo componentInfo)
		{
			return true;
		}

		#endregion

		#region Other private methods

		/// <summary>
		/// Loads a single assembly file as an NQ component.
		/// </summary>
		/// <param name="assemblyFile">Name of assembly file.</param>
		private void LoadComponentAssembly(string assemblyFile)
		{
			// Try to load the DLL as a .NET assembly
			try
			{
				AssemblyName asmName = AssemblyName.GetAssemblyName(assemblyFile);
				if (!_loadedAssemblies.Contains(asmName.Name))
				{
					AppDomain.CurrentDomain.Load(asmName);
					_loadedAssemblies.Add(assemblyFile);
				}
			}
			catch (BadImageFormatException)
			{
				// Do nothing as it might be possible that the DLL is not a .NET assembly
			}
		}

		#endregion
	}
}
